using System;
using System.Collections.Generic;
using System.Text;
using Adoor.Object.Domain;
using Adoor.Object.Disconnected;
using Adoor.Object.Entity;
using System.Transactions;

using System.ComponentModel;
using System.Drawing.Design;
using System.Collections;


namespace Adoor.Object.Domain
{
    /// <summary>
    /// Create a direct mapping between a type
    /// </summary>
    [Serializable, DefaultProperty("Domains")
    //,Editor(typeof(EditorTest2), typeof(UITypeEditor))
    ]// Editor("Adoor.VSDesigner.CollectionEditor.LinkedDomainGroupEditor, Adoor.VSDesigner", typeof(UITypeEditor))]
    public class LinkedDomainGroup //: CollectionBase, ICollection<ObjectDomain>
    {
        //private ICollection<ObjectDomain> m_innerList = new ICollection<ObjectDomain>();

        //protected static log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        IObjectSource remoteSource;
        
        public LinkedDomainGroup()
        {
        }

        public IObjectResolver objectResolver;
        public IObjectResolver ObjectResolver
        {
            get
            {
                return objectResolver;
            }
            set
            {
                objectResolver = value;
                if (RemoteSource != null)
                    RemoteSource.ObjectResolver = this.ObjectResolver;
            }
        }

        [Category("Behavior"), TypeConverter(typeof(ExpandableObjectConverter)),
       System.ComponentModel.EditorBrowsable(EditorBrowsableState.Never),
      DescriptionAttribute("Add Domains to this Group"),
      DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public ObjectDomainCollection Domains
        {
            get { return this.flattenedDomainList; }
        }

        [Category("Behavior"),
       DescriptionAttribute("Bind group to a Remote source"),
       DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
       TypeConverter(typeof(ExpandableObjectConverter))]
        public IObjectSource RemoteSource
        {
            get 
            { 
                return remoteSource; 
            }
            set 
            { 
                remoteSource = value;
                remoteSource.ObjectResolver = this.ObjectResolver;
            }
        }

        

        /// <summary>
        /// Retreive EntityData from a given Type.
        /// Iterate throught all ObjectDomain in Group
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public EntityData FindEntityDataForType(Type type)
        {
            foreach (ObjectDomain domain in domainCache.Values)
            {
                EntityData ed = null;
                if ( domain.EntityInfos.FactoriesByType.TryGetValue(type, out ed))
                    return ed;
            }
            return null;
        }

        #region Manage domain in group

        private ObjectDomainCollection flattenedDomainList = new ObjectDomainCollection();

        /// <summary>
        /// Retreive all doamin from group
        /// </summary>
        //[ObsoleteAttribute("shouldn't we return a clone rather than cached orginal list ? callers may altern cached version", false)]
        public ObjectDomainCollection FlattenedDomains
        {
            get
            {
                if (flattenedDomainList != null)
                    return flattenedDomainList;

                flattenedDomainList = new ObjectDomainCollection();
                flattenedDomainList.CollectionChanged += new Adoor.Collections.Generic.CollectionChangeEventHandler<ObjectDomain>(flattenedDomainList_CollectionChanged); 

                foreach (ObjectDomain domain in domainCache.Values)
                {
                    flattenDomainReferences(domain);
                }

                return flattenedDomainList;
            }
        }

        void flattenedDomainList_CollectionChanged(object sender, Adoor.Collections.Generic.CollectionChangeEventArgs<ObjectDomain> e)
        {
            switch (e.Action)
            {
                case Adoor.Collections.Generic.CollectionChangeAction.Add :
                    this.Add(e.Element); 
                    break;
                case Adoor.Collections.Generic.CollectionChangeAction.Remove :
                    this.Remove(e.Element);
                    break;
            }
        }


        private void flattenDomainReferences(ObjectDomain domain)
        {
            if (flattenedDomainList.Contains(domain))
                return;

            flattenedDomainList.Add(domain);
            foreach (ObjectDomain child in domain.References)
                flattenDomainReferences(child);
        }
        #endregion

        public void Validate(IObjectStorage LocalStorage, ObjectContext context)
        {
            foreach (ObjectDomain domain in FlattenedDomains)
            {
                foreach (EntityData entityData in domain.EntityInfos)
                {
                    IEntityFactory factory = (IEntityFactory)entityData.GetService(typeof(IEntityFactory));
                    if (factory != null)
                        factory.Validate(LocalStorage, context);
                }
            }
        }

        private List<EntityData> FindDependancyList(EntityData entityData,ObjectDomain domain)
        {
            List<EntityData> result = new List<EntityData>();

            if (entityData.EntityType.FullName == "InfoComplementaire")
                System.Diagnostics.Trace.WriteLine("");

            result.Add(entityData);
            
            if (domain.ObjectDependencies[entityData] != null)
            {
                foreach (DependantEntity entityDataDep in domain.ObjectDependencies[entityData])
                {
                    //result.AddRange(FindDependancyList(entityDataDep.ForeignEntity, domain));
                    foreach (EntityData dep in FindDependancyList(entityDataDep.ForeignEntity, domain))
                    {
                        if (result.Contains(dep))
                        {
                            int idx = result.IndexOf(dep);  
                            result.RemoveAt(idx); 
                            result.Add(dep);
                        }
                        else
                        result.Add(dep);

                    }
                }
            }
            
            return result;
        }

        public void FilterUpdaterList(List<IEntityUpdater> col)
        {
            for (int i = col.Count-1; i > 0; i--)
            {
                IEntityUpdater ent = col[i];
                int idx = col.LastIndexOf(ent);
                if (idx != i)
                    col.RemoveAt(i); 
            }

        }
        /// <summary>
        /// Perform update of all the entitties in domainGroup
        /// </summary>
        /// <param name="context"></param>
        public void Update(ObjectContext context)
        {
            Dictionary<IEntityUpdater, EntityData> dict = new Dictionary<IEntityUpdater, EntityData>();
            using (TransactionScope transaction = new TransactionScope())
            {
                List<string> done = new List<string>();
                List<IEntityUpdater> updaters = new List<IEntityUpdater>();
                foreach (ObjectDomain domain in FlattenedDomains)
                { 
                    foreach (EntityData entityData in domain.EntityInfos)
                    {

                        List<EntityData>  col = FindDependancyList(entityData, domain);
                       
                        IEntityUpdater previous = null;
                        foreach (EntityData entityDataDep in col)
                        {
                            //IEntityUpdater updater = (IEntityUpdater)entityData.GetService(typeof(IEntityUpdater));
                            IEntityUpdater updater = (IEntityUpdater)entityDataDep.GetService(typeof(IEntityUpdater));
                            
                            if (updater != null)
                            {
                                if (!dict.ContainsKey(updater))
                                    dict.Add(updater, entityDataDep);  
                                //if (!updaters.Contains(updater))
                                //{
                                //    int idx = updaters.Count;
                                //    if (previous != null)
                                //        idx = updaters.IndexOf(previous); 
                                //    
                                //    updaters.Insert(idx,updater);
                                updaters.Add(updater);
                                //}
                            }
                            previous = updater;
                        }//foreach dep in col

                        IEntityUpdater test = (IEntityUpdater)entityData.GetService(typeof(IEntityUpdater));
                        if (!dict.ContainsKey(test))
                            dict.Add(test, entityData);

                        //IEntityUpdater updater = (IEntityUpdater)entityData.GetService(typeof(IEntityUpdater));
                        //if (updater != null)
                        //    updaters.Add(updater);                            
                    }//for each in domain
                }//for each domain
                 
                FilterUpdaterList(updaters);

                foreach (IEntityUpdater e in updaters)
                {
                    //IEntityUpdater u = (IEntityUpdater)e.GetService(typeof(IEntityUpdater));
                    int idx = updaters.IndexOf(e);
                    EntityData uu = dict[e];
                    if (uu.Domain.ObjectDependencies[uu] != null)
                    {
                        foreach (DependantEntity de in uu.Domain.ObjectDependencies[uu])
                        {
                            IEntityUpdater du = (IEntityUpdater)de.ForeignEntity.GetService(typeof(IEntityUpdater));
                            int idx2 = updaters.IndexOf(du);
                            if (idx2 < idx)
                                System.Diagnostics.Trace.WriteLine("");
                        }
                    }
                }

                List<IEntityUpdater> reversed =  new List<IEntityUpdater>();
                reversed.AddRange(updaters);
                reversed.Reverse();

               
                foreach (IEntityUpdater updater in updaters)
                {
                    updater.UpdateInsertEntities(context);
                }

                foreach (IEntityUpdater updater in updaters)
                {
                    updater.UpdateUpdatedEntities(context);
                }

                foreach (IEntityUpdater updater in updaters)
                {
                    updater.UpdateRelations(context);
                }

                foreach (IEntityUpdater updater in reversed)
                {
                    updater.UpdateDeletedEntities(context);
                }


                (context.LocalStorage as ICacheCommitable).CommitLocalCache(); 
                transaction.Complete();
            }
        }

        private bool built;
        public void Build()
        {
            if (!built)
            {
                built = true;
                foreach (ObjectDomain domain in FlattenedDomains)
                {
                    domain.Build();
                }
            }
        }

        private Dictionary<string, ObjectDomain> domainCache = new Dictionary<string,ObjectDomain>();

        public Adoor.Object.Domain.ObjectDomain Add(System.Reflection.Assembly assembly, string resourceName)
        {
            string path = string.Empty; 
            if (!string.IsNullOrEmpty(resourceName))
                path = string.Concat(assembly.GetName().Name, "#", resourceName);
            return Add(assembly.GetName().Name, assembly.FullName, path);
        }

        public Adoor.Object.Domain.ObjectDomain Add(string AssemblyName, string domainName, string resourceName)
        {
            Adoor.Object.Domain.ObjectDomain domain = new Adoor.Object.Domain.ObjectDomain(this, domainName);
            domain.AssemblyName = AssemblyName;
            if (!string.IsNullOrEmpty(resourceName))
                domain.MapFileName = resourceName;
            Add(domain);
            return domain;
        }

        /// <summary>
        /// add all dependent assemblies
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="assembly"></param>
        /// <param name="resourceName"></param>
        private void addDomainReferences(Adoor.Object.Domain.ObjectDomain parent)
        {
            System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(parent.AssemblyName);
            foreach (System.Reflection.AssemblyName assemblyName in assembly.GetReferencedAssemblies())
            {
                System.Reflection.Assembly assemblyRef = System.Reflection.Assembly.Load(assemblyName);
                string newResourceName = parent.MapFileName;
                int idxSharp = newResourceName.IndexOf('#');
                if (idxSharp >= 0)
                    newResourceName = newResourceName.Substring(idxSharp + 1);

                if (newResourceName.StartsWith(assembly.GetName().Name))
                    newResourceName = assemblyRef.GetName().Name + newResourceName.Substring(assembly.GetName().Name.Length);

                Adoor.Object.Domain.ObjectDomain reference;
                
                if(!domainCache.TryGetValue(assemblyName.FullName, out reference))
                {
                    System.IO.Stream s = assemblyRef.GetManifestResourceStream(newResourceName);
                    if (s != null)
                    {
                        reference = Add(assemblyRef, newResourceName);
                        if (reference != null)
                            parent.References.Add(reference);
                    }
                }
                else
                    parent.References.Add(reference);
            }
        }

        /// <summary>
        /// Clear the domain cache
        /// </summary>
        public void Clear()
        {
            built = false;
            domainCache = null;
            flattenedDomainList = null;
        }


        #region ICollection<ObjectDomain> Members

        public void Add(ObjectDomain item)
        {
            if (built)
                throw new Exception("Cannot add ObjectDomain when the group has been built.");

            if (domainCache == null)
                domainCache = new Dictionary<string, ObjectDomain>();

            if (!domainCache.ContainsKey(item.AssemblyName))
            {
                AdoorLoger.Debug("Added Domain " + item.AssemblyName);
                domainCache.Add(item.AssemblyName, item);
                flattenedDomainList.Add(item);   
                addDomainReferences(item);                
            }            
        }

        public bool Contains(ObjectDomain item)
        {
            return domainCache.ContainsValue(item);
        }

        public void CopyTo(ObjectDomain[] array, int arrayIndex)
        {
            foreach (ObjectDomain domain in domainCache.Values)
            {
                if (arrayIndex > array.Length)
                    break;
                array[arrayIndex++] = domain;
            }
        }

        public int Count
        {
            get { return domainCache.Count; }
        }

        public bool IsReadOnly
        {
            get 
            {
                return built;
            }
        }

        public bool Remove(ObjectDomain item)
        {
            throw new NotSupportedException();
        }

        #endregion

        #region IEnumerable<ObjectDomain> Members

        public IEnumerator<ObjectDomain> GetEnumerator()
        {
            return domainCache.Values.GetEnumerator();
        }

        #endregion

        //#region IEnumerable Members

        //System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        //{
        //    return ((System.Collections.IEnumerable)domainCache.Values).GetEnumerator();
        //}

        //#endregion
    }

    
}
